import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

public class JavaCADU extends DrawObject
{
    public static class JavaCADUParameterSet
    {
        private int AxonCount;

        private int DendritesCount;
        private int[] AllowedDelaysInTicks;
        private int LearningNeuronCount;

        public JavaCADUParameterSet(int AxonCount, int DendritesCount, int[] AllowedDelaysInTicks, int LearningNeuronCount)
        {
            this.AxonCount = AxonCount;
            this.DendritesCount = DendritesCount;
            this.AllowedDelaysInTicks = AllowedDelaysInTicks;
            this.LearningNeuronCount = LearningNeuronCount;
        }

        public int[] GetAllowedDelaysInTicks()
        {
            return AllowedDelaysInTicks;
        }

        public int GetAxonCount()
        {
            return AxonCount;
        }

        public int GetDendritesCount()
        {
            return DendritesCount;
        }

        public int GetLearningNeuronCount()
        {
            return LearningNeuronCount;
        }
    }

    // DrawObject abilities:
    @Override
    public boolean IsJavaCADU()
    {
        return true;
    }

    @Override
    public boolean CanBeDeletedByUser()
    {
        return true;
    }

    @Override
    public boolean CanBeExcitedByUser()
    {
        return false;
    }

    @Override
    public boolean CanBeMovedByUser()
    {
        return true;
    }

    @Override
    public boolean CanBeSelectedByUser()
    {
        return true;
    }
    // end of DrawObject abilities

    private ArrayList<JavaCADUInputNode> InputNodes = new ArrayList<JavaCADUInputNode>();
    private ArrayList<JavaCADULearningNode> LearningNodes = new ArrayList<JavaCADULearningNode>();
    private ArrayList<JavaCADUOutputNode> OutputNodes = new ArrayList<JavaCADUOutputNode>();

    private boolean[][] VerticalNodesActivated = new boolean[0][0]; // JavaCADU specific; declared here to make DrawObject.ResetState() work for this
    private HashMap<String, Integer> VerticalNodesActivationPatternStorage = new HashMap<String, Integer>();

    public JavaCADU(Point Pos)
    {
        SetPos(Pos);
        SetSize(new Point(100, 100));
        InitializeExcitement(ExcitementOnceMilliseconds);
        SetCompoundId(GetNewCompoundId());
    };

    public ArrayList<DrawObject> AddChildDrawObjects(int InputNodeCount, int LearningNodeCount, int[] AllowedDelaysInTicks, int OutputNodeCount)
    { // spawns child objects

        ArrayList<DrawObject> ChildDrawObjects = new ArrayList<DrawObject>();

        for (int i = 0; i < InputNodeCount; i++)
        {
            Point JavaCADUInputNodePos = new Point(Pos.x - 10, Pos.y + InputNodes.size() * 20);

            JavaCADUInputNode JavaCADUInputNodeNew = new JavaCADUInputNode(JavaCADUInputNodePos);

            JavaCADUInputNodeNew.SetCompoundId(((JavaCADU) this).CompoundId);
            JavaCADUInputNodeNew.SetParentDrawObject(this);
            JavaCADUInputNodeNew.ExcitementPartner = (JavaCADU) this;

            InputNodes.add(JavaCADUInputNodeNew);

            ChildDrawObjects.add(JavaCADUInputNodeNew);
        }

        for (int i = 0; i < LearningNodeCount; i++)
        {
            Point JavaCADULearningNodePos = new Point(Pos.x - 10, Pos.y + InputNodes.size() * 20 + 20 + LearningNodes.size() * 20);

            JavaCADULearningNode JavaCADULearningNodeNew = new JavaCADULearningNode(JavaCADULearningNodePos);

            JavaCADULearningNodeNew.SetCompoundId(((JavaCADU) this).CompoundId);
            JavaCADULearningNodeNew.SetParentDrawObject(this);
            JavaCADULearningNodeNew.ExcitementPartner = (JavaCADU) this;

            LearningNodes.add(JavaCADULearningNodeNew);

            ChildDrawObjects.add(JavaCADULearningNodeNew);
        }

        for (int i = 0; i < OutputNodeCount; i++)
        {
            Point JavaCADUOutputNodePos = new Point(Pos.x - 10, Pos.y + InputNodes.size() * 20 + 20 + LearningNodes.size() * 20 + 20 + OutputNodes.size() * 20);

            JavaCADUOutputNode JavaCADUOutputNodeNew = new JavaCADUOutputNode(JavaCADUOutputNodePos);

            JavaCADUOutputNodeNew.SetCompoundId(((JavaCADU) this).CompoundId);
            JavaCADUOutputNodeNew.SetParentDrawObject(this);
            this.ExcitementPartner = JavaCADUOutputNodeNew;

            OutputNodes.add(JavaCADUOutputNodeNew);

            ChildDrawObjects.add(JavaCADUOutputNodeNew);
        }

        AutoSize();

        // COMPARE TO CADUEventProcessor.CreateCADU()!!! >>>
        int AllowedDelayInTicksMax = 0;

        for (int m = 0; m < AllowedDelaysInTicks.length; m++)
            if (AllowedDelaysInTicks[m] > AllowedDelayInTicksMax)
                AllowedDelayInTicksMax = AllowedDelaysInTicks[m];

        VerticalNodesActivated = new boolean[InputNodeCount][InputNodeCount * (AllowedDelayInTicksMax + 1)];

        for (int m = 0; m < VerticalNodesActivated.length; m++)
            for (int n = 0; n < VerticalNodesActivated[m].length; n++)
                VerticalNodesActivated[m][n] = false; // preset
        // <<<

        StrengthMax = (VerticalNodesActivated.length > 0 ? (VerticalNodesActivated[0].length) : 0); // alienated usage of Strength to save memory in RAM and .ncide file

        return ChildDrawObjects;
    }

    public void AddInputNode(JavaCADUInputNode Node)
    {
        Node.SetParentDrawObject(this);
        Node.ExcitementPartner = this;
        InputNodes.add(Node);

        int VerticalNodeCount = StrengthMax; // alienated usage of Strength to save memory in RAM and .ncide file
        VerticalNodesActivated = new boolean[InputNodes.size()][VerticalNodeCount];

        AutoSize();
    }

    public void AddLearningNode(JavaCADULearningNode Node)
    {
        Node.SetParentDrawObject(this);
        Node.ExcitementPartner = this;
        LearningNodes.add(Node);

        int VerticalNodeCount = StrengthMax; // alienated usage of Strength to save memory in RAM and .ncide file
        VerticalNodesActivated = new boolean[InputNodes.size()][VerticalNodeCount];

        AutoSize();
    }

    public void AddOutputNode(JavaCADUOutputNode Node)
    {
        Node.SetParentDrawObject(this);
        this.ExcitementPartner = Node;
        OutputNodes.add(Node);

        int VerticalNodeCount = StrengthMax; // alienated usage of Strength to save memory in RAM and .ncide file
        VerticalNodesActivated = new boolean[InputNodes.size()][VerticalNodeCount];

        AutoSize();
    }

    @Override
    protected void AfterCollectingAllExcitementForNextTick()
    {
        // shift boolean values, which simulates the moving of
        // excitement over a Line to the next Node in the
        // original CADU:

        for (int m = 0; m < VerticalNodesActivated.length; m++)
        {
            for (int n = VerticalNodesActivated[m].length - 1; n >= 1; n--)
                VerticalNodesActivated[m][n] = VerticalNodesActivated[m][n - 1];

            VerticalNodesActivated[m][0] = false;
        }
    }

    private void AutoSize()
    {
        if ((InputNodes.size() * 20 + 20 + LearningNodes.size() * 20 + 20 + OutputNodes.size() * 20 + 20) > 100)
        {
            Size.y = InputNodes.size() * 20 + 20 + LearningNodes.size() * 20 + 20 + OutputNodes.size() * 20 + 20;
        }
        else
        {
            Size.y = 100;
        }
    }

    @Override
    public void Draw(Graphics g)
    {
        if (!(IsWithinWindowBounds(this, 0)))
            return;

        // set font once for speed reasons
        g.setFont(GetSmallFont());

        // background (overdraw grid)
        if (IsHighlighted || IsPartOfMultiSelection)
            SetColor(g, Color.GREEN);
        else
            SetColor(g, Color.LIGHT_GRAY);
        FillRect(g, Pos.x, Pos.y, Size.x, Size.y);

        // child objects
        /* // Beta 3: bug fixed: no need for manual Draw() call, system calls Draw() for every DrawObject anyway!
        for (Iterator<JavaCADUInputNode> i = InputNodes.iterator(); i.hasNext();)
        {
        	JavaCADUInputNode o = i.next();
        	o.Draw(g);
        }
        for (Iterator<JavaCADULearningNode> i = LearningNodes.iterator(); i.hasNext();)
        {
        	JavaCADULearningNode o = i.next();
        	o.Draw(g);
        }
        for (Iterator<JavaCADUOutputNode> i = OutputNodes.iterator(); i.hasNext();)
        {
        	JavaCADUOutputNode o = i.next();
        	o.Draw(g);
        }
        */

        // info text
        SetColor(g, Color.BLACK);

        DrawString(g, "always if", Pos.x + 6, Pos.y + 12);
        DrawString(g, "this (in)", Pos.x + 6, Pos.y + 12 + 12);

        DrawString(g, "and once", Pos.x + 6, Pos.y + 12 + InputNodes.size() * 20 + 20);
        DrawString(g, "this (in)", Pos.x + 6, Pos.y + 12 + InputNodes.size() * 20 + 20 + 12);

        DrawString(g, "then always", Pos.x + 6, Pos.y + 12 + InputNodes.size() * 20 + 20 + LearningNodes.size() * 20 + 20);
        DrawString(g, "this (out)", Pos.x + 6, Pos.y + 12 + InputNodes.size() * 20 + 20 + LearningNodes.size() * 20 + 20 + 12);

        DrawString(g, (StrengthMax / Simulation.TicksPerIOTextAreaLine()) + " IO lines", Pos.x + 6, Pos.y + InputNodes.size() * 20 + 20 + LearningNodes.size() * 20 + 20 + 50);
        DrawString(g, "regarded", Pos.x + 6, Pos.y + InputNodes.size() * 20 + 20 + LearningNodes.size() * 20 + 20 + 50 + 12);
        DrawString(g, "at once", Pos.x + 6, Pos.y + InputNodes.size() * 20 + 20 + LearningNodes.size() * 20 + 20 + 50 + 12 + 12);

        // Frame
        SetColor(g, Color.BLACK);
        for (int i = 0; i < OutputNodes.size(); i++)
        {
            if (OutputNodes.get(i).DoesActivate(OutputNodes.get(i).GetExcitementMaxFromExcitementHistory())) // (minor) hack: IsActivated() does not work (because excitement reset when IsActivated() called "at the wrong time"), involve ExcitementHistory
            {
                SetColor(g, OutputNodes.get(i).GetExcitementColor()); // JavaCADU DrawObject itself can't be activated (it only provides ExciteAsPartner()), so take color from activated Node
                break;
            }
        }
        DrawRect(g, Pos.x, Pos.y, Size.x, Size.y);
    }

    protected void ExciteAsPartner(double ExcitementAdd, DrawObject Sender)
    {
        if (DoesActivate(ExcitementAdd))
        {
            for (int i = 0; i < InputNodes.size(); i++)
            {
                if (InputNodes.get(i) == Sender)
                {
                    // System.out.println("JavaCADU.InputNode #" + i + " sent excitement");

                    if (i < VerticalNodesActivated.length && VerticalNodesActivated[i].length > 0) // should always be the case, maybe except when the user runs a simulation, loads a file and activates nodes concurrently (worst case)
                        VerticalNodesActivated[i][0] = true;
                }
            }
            for (int i = 0; i < LearningNodes.size(); i++)
            {
                if (LearningNodes.get(i) == Sender)
                {
                    // System.out.println("JavaCADU.LearningNode #" + i + " sent excitement");

                    String ActivationPattern = GetActivationPatternString(VerticalNodesActivated);

                    if (ActivationPattern != null) // any activation existing at all?
                    {
                        // System.out.println("Saving ActivationPattern: " + ActivationPattern);

                        SaveVerticalNodesActivatedPattern(ActivationPattern, i);
                    }
                }
            }
        }
    }

    private String GetActivationPatternString(boolean[][] VerticalNodesActivated)
    { // returns string, or null if no useful string creatable

        if (VerticalNodesActivated.length <= 0 || VerticalNodesActivated[0].length <= 0) // should never be the case, maybe except the user has running a simulation, loads a file and activates nodes concurrently (if possible at all)
            return null;

        StringBuilder SB = new StringBuilder(2 + VerticalNodesActivated.length * VerticalNodesActivated[0].length + VerticalNodesActivated.length * 2 + 10); // add some safety

        boolean AnyActivationExisting = false;

        SB.append("\r\n");
        for (int m = 0; m < VerticalNodesActivated.length; m++)
        {
            for (int n = 0; n < VerticalNodesActivated[m].length; n++)
            {
                if (VerticalNodesActivated[m][n] == true)
                {
                    AnyActivationExisting = true;
                    SB.append("1"); // can be any char, just different from the 'false' case below
                }
                else
                {
                    SB.append(".");
                }
            }
            SB.append("\r\n");
        }

        if (!(AnyActivationExisting))
        {
            return null;
        }
        else
        {
            String ActivationPattern = SB.toString();

            return ActivationPattern;
        }
    }

    @Override
    public ArrayList<DrawObject> GetChildDrawObjects()
    {
        ArrayList<DrawObject> ChildDrawObjects = new ArrayList<DrawObject>();

        for (Iterator<JavaCADUInputNode> i = InputNodes.iterator(); i.hasNext();)
        {
            JavaCADUInputNode o = i.next();
            ChildDrawObjects.add(o);
        }
        for (Iterator<JavaCADULearningNode> i = LearningNodes.iterator(); i.hasNext();)
        {
            JavaCADULearningNode o = i.next();
            ChildDrawObjects.add(o);
        }
        for (Iterator<JavaCADUOutputNode> i = OutputNodes.iterator(); i.hasNext();)
        {
            JavaCADUOutputNode o = i.next();
            ChildDrawObjects.add(o);
        }

        return ChildDrawObjects;
    }

    @Override
    protected void LoseExcitementAndAddExcitementForNextTick()
    {
        String ActivationPattern = GetActivationPatternString(VerticalNodesActivated);

        if (ActivationPattern != null) // any activation existing at all?
        {
            // System.out.println("DETECTED: " + ActivationPattern);

            ReactOnVerticalNodesActivatedPattern(ActivationPattern);
        }
    }

    @Override
    public void Move(Point MoveAmount)
    {
        for (Iterator<JavaCADUInputNode> i = InputNodes.iterator(); i.hasNext();)
        {
            JavaCADUInputNode o = i.next();
            o.Move(MoveAmount);
        }
        for (Iterator<JavaCADULearningNode> i = LearningNodes.iterator(); i.hasNext();)
        {
            JavaCADULearningNode o = i.next();
            o.Move(MoveAmount);
        }
        for (Iterator<JavaCADUOutputNode> i = OutputNodes.iterator(); i.hasNext();)
        {
            JavaCADUOutputNode o = i.next();
            o.Move(MoveAmount);
        }

        Pos.x += MoveAmount.x;
        Pos.y += MoveAmount.y;
    };

    private void ReactOnVerticalNodesActivatedPattern(String ActivationPattern)
    {
        // System.out.println("JavaCADU activation pattern string: " + ActivationPattern);

        Integer OutputNodeToBeActivated = VerticalNodesActivationPatternStorage.get(ActivationPattern);
        if (OutputNodeToBeActivated != null &&
            OutputNodeToBeActivated.intValue() >= 0 && OutputNodeToBeActivated.intValue() < OutputNodes.size())
        {
            // System.out.println("JavaCADU OUTPUT #" + OutputNodeToBeActivated.intValue() + " ACTIVATED!");

            JavaCADUOutputNode o = OutputNodes.get(OutputNodeToBeActivated.intValue());

            o.ExciteAsPartner(1.0, o); // to activate OutputNode
        }
    }

    protected void ResetJavaCADU()
    {
        for (int m = 0; m < VerticalNodesActivated.length; m++)
            for (int n = 0; n < VerticalNodesActivated[m].length; n++)
                VerticalNodesActivated[m][n] = false; // reset

        VerticalNodesActivationPatternStorage.clear();
    }

    private void SaveVerticalNodesActivatedPattern(String ActivationPattern, int ActivatedLearningNodeIndex)
    {
        // System.out.println("JavaCADU activation pattern string: " + ActivationPattern);

        if (VerticalNodesActivationPatternStorage.get(ActivationPattern) == null)
        {
            VerticalNodesActivationPatternStorage.put(ActivationPattern, ActivatedLearningNodeIndex);

            // System.out.println("saved JavaCADU activation pattern");
        }
    }

    @Override
    public void SetHighlighted(boolean IsHighlightedNew)
    {
        IsHighlighted = IsHighlightedNew;

        for (Iterator<JavaCADUInputNode> i = InputNodes.iterator(); i.hasNext();)
        {
            JavaCADUInputNode o = i.next();
            o.SetHighlighted(IsHighlighted);
        }
        for (Iterator<JavaCADULearningNode> i = LearningNodes.iterator(); i.hasNext();)
        {
            JavaCADULearningNode o = i.next();
            o.SetHighlighted(IsHighlighted);
        }
        for (Iterator<JavaCADUOutputNode> i = OutputNodes.iterator(); i.hasNext();)
        {
            JavaCADUOutputNode o = i.next();
            o.SetHighlighted(IsHighlighted);
        }
    }

    @Override
    public void SetPartOfMultiSelection(boolean IsPartNew)
    {
        IsPartOfMultiSelection = IsPartNew;

        for (Iterator<JavaCADUInputNode> i = InputNodes.iterator(); i.hasNext();)
        {
            JavaCADUInputNode o = i.next();
            o.SetPartOfMultiSelection(IsPartOfMultiSelection);
        }
        for (Iterator<JavaCADULearningNode> i = LearningNodes.iterator(); i.hasNext();)
        {
            JavaCADULearningNode o = i.next();
            o.SetPartOfMultiSelection(IsPartOfMultiSelection);
        }
        for (Iterator<JavaCADUOutputNode> i = OutputNodes.iterator(); i.hasNext();)
        {
            JavaCADUOutputNode o = i.next();
            o.SetPartOfMultiSelection(IsPartOfMultiSelection);
        }
    }

    @Override
    public void SetPos(Point PosNew)
    {
        PosNew = AlignOnGrid(PosNew);

        Point MoveAmount = new Point(PosNew.x - Pos.x, PosNew.y - Pos.y);

        Move(MoveAmount);
    }

    public void Toggle()
    {
        Toggle(Simulation.TicksPerIOTextAreaLine());
    }

    public void ToggleIsNegative()
    {
        Toggle(10 * Simulation.TicksPerIOTextAreaLine() - (StrengthMax % (10 * Simulation.TicksPerIOTextAreaLine())));
    }

    private void Toggle(int StrengthMaxAdd)
    {
        // StrengthMax is kind of misused here to save how
        // long the pattern succession the JavaCADU detects
        // can be - user can change this value by right-
        // clicking on JavaCADU.

        StrengthMax = ((int) (StrengthMax / Simulation.TicksPerIOTextAreaLine())) * Simulation.TicksPerIOTextAreaLine(); // round down
        StrengthMax += StrengthMaxAdd;
        if (StrengthMax > 50 * Simulation.TicksPerIOTextAreaLine())
            StrengthMax = 1 * Simulation.TicksPerIOTextAreaLine();
    }
}
